そろそろConcurrent Modeを真面目に考えよう
終了した駄文
モチベーション
Amamoを開発したときの知見を一般化して残す
React Concurrent Renderingについて、RelayやReact Server Componentsを絡めつつ議論する
Concurrent Mode時代のReact設計論の後途絶えている日本語でのReact Concurrent Renderingの実践的なドキュメントが欲しい
↑で書かれていない状態管理と絡めた話をしたい
useSyncExternalStoreとか
注意
React Concurrent Renderingの基本的挙動を理解していることを前提とします
「Promiseをthrowするんだよね」とか「Suspenseでフォールバックを表示するんだよね」とか「startTransitionで状態変化前と変化後を分岐させるんだよね」みたいなことを知っていればいい
原則
非同期なデータを扱うオブジェクトを使う
React Concurrent Rendering以前のReactでは非同期処理後、その結果をReduxやコンポーネントの状態として格納していました。しかしReact Concurrent RenderingではSuspenseを利用してPromiseを処理する責任を下位のコンポーネントに移譲する関係上、Promiseをラップするオブジェクトを状態として管理するほうが都合が良いです。
このようなオブジェクトの実装例として
Concurrent Mode時代のReact設計論 (1) Concurrent Modeにおける非同期処理で挙げられているFetcher<T>
Promiseを受け取り、状態に合わせてgetでPromiseをthrowしたり値を返したりします
RecoilのLoadable<T>
https://github.com/facebookexperimental/Recoil/blob/master/src/adt/Recoil_Loadable.js
Relayの各種Reference
QueryReferenceやFragmentReference
などが挙げられるでしょう。
AmamoではFetcher<T>と同様の実装をLoadable<T>という名前で利用しました。
https://github.com/tosuke/amamo/blob/56a4717a16e10a3a22571d6cf3a4e80d21b6f5f2/src/utils/Loadable.ts
React Server Componentsにおいてはこのようなオブジェクトは出現せず、代わりにキャッシュ機構によって非同期状態の管理を行っているようです
https://github.com/reactjs/server-components-demo/blob/main/src/Cache.client.js
これもう少し研究が必要だな、だめそうだったら記事から外すか
react-cache がreact内に含まれるようになったとか全く聞いてないが
FlightResponseが非同期状態管理オブジェクトで,.readRoot()でReactElementを返す
非同期処理の実行と結果の利用を分離する
非同期処理(というかIO)が実行されるとき、アプリケーションでは必ずと言っていいほど何らかのイベントが発生しています。
useEffectでマウント時に実行させているぞ!という人もいるかもしれませんが、その背後ではより重要なイベントが発生しています。本来はそのタイミングで非同期処理を実行すべきでしょう。
ページ遷移(リンクのクリック、戻る操作など)、後述します
そこで、上位のコンポーネントではイベントに沿って非同期処理を実行して非同期データを状態として保持し、下位のコンポーネントでは非同期データの結果を利用するようにします。このことによって、今までContainer Componentと呼ばれていたものが2種類に分かれることになります。
1. イベントをハンドリングし、実際に非同期処理を実行するコンポーネント
RelayにおいてはuseQueryLoaderを持つコンポーネントがこの役割を持ちます
ReduxでuseDispatchを利用しているコンポーネント
2. 自身に必要なデータを宣言し、利用するコンポーネント
RelayにおいてはusePreloadedQueryやuseFragmentを持つコンポーネントがこの役割を持ちます
usePreloadedQueryはloadQueryやuseQueryLoaderとセットなので独立はしていなさそう。useFragmentは汎用的
ReduxでuseSelectorを利用しているコンポーネント
この考え方については、RelayのusePreloadedQuery のドキュメントを読むとわかりやすいと思います
#TODO